package rpc.core.registry;

import com.google.common.collect.Lists;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.util.StringUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rpc.bootstrap.spring.RefererBean;
import rpc.bootstrap.spring.RegistryBean;
import rpc.bootstrap.spring.ServiceBean;
import rpc.core.Registry;
import rpc.utils.DateUtils;
import rpc.utils.IpUtils;
import rpc.utils.JsonUtil;
import rpc.utils.ReflectionUtils;
import rpc.utils.TypeSafe;

public class ConsulRegistry implements Registry {

  private static final Logger logger = LoggerFactory.getLogger(ConsulRegistry.class);
  private final Set<String> serviceNameSet = new HashSet<>();

  public ConsulRegistry() {
    logger.info("init");
  }

  @Override
  public void register(ServiceBean serviceBean, RegistryBean registryBean) {

    if (serviceNameSet.contains(serviceBean.getInterfaceName())) {
      return;
    }

    String addr = "http://" + registryBean.getAddr() + "/v1/agent/service/register";
    Map<String, Object> registryMap = new HashMap<>();
    String serviceId = String
        .valueOf(ReflectionUtils.getFliedValue(serviceBean.getInterfaceClass(), "serviceId"));
//    registryMap.put("ID", serviceBean.getServerId());
    registryMap.put("ID", DigestUtils.md5Hex(serviceBean.getInterfaceName() + "-" + IpUtils.localIp()));
    String serviceName = serviceBean.getInterfaceClass().getSimpleName();
    registryMap.put("Name", serviceId + "-" + serviceName);
    registryMap.put("address", IpUtils.localIp());
    registryMap.put("Port", serviceBean.getPort());
    Map<String, Object> checkMap = new HashMap<>();
    checkMap.put("Name", "check_" + serviceId);
    checkMap.put("Status", "passing");
    checkMap.put("Interval", "10s");
    checkMap.put("Timeout", "1s");
//    checkMap.put("TCP", IpUtils.localIp() + ":" + serviceBean.getCheckPort());
//    DeregisterCriticalServiceAfter  最小1分钟
    checkMap.put("DeregisterCriticalServiceAfter", "1m");
    checkMap.put("HTTP", "http://" + IpUtils.localIp() + ":" + serviceBean.getCheckPort() + "/check");
//    checkMap.put("TTL", "10ms");
    registryMap.put("Check", checkMap);
    Map<String, Object> metadata = new HashMap<>();
    metadata.put("group", serviceBean.getGroup());
    metadata.put("regTime", DateUtils.format(new Date()));
    String methodHash = serviceBean.getMethodMap().values().stream().map(Method::getName).collect(Collectors.joining("-")).hashCode() + "";
    metadata.put("methodSize", "" + serviceBean.getMethodMap().values().size());
    metadata.put("methodHash", methodHash);
    registryMap.put("Meta", metadata);
    String r = JsonUtil.toJson(registryMap);

    try {
      HttpResponse httpResponse = HttpRequest.put(addr).contentType("application/json")
          .charset("utf-8").body(r).send();
      if (StringUtil.isBlank(httpResponse.bodyText())) {
        serviceNameSet.add(serviceBean.getInterfaceName());
      }
      if (logger.isDebugEnabled()) {
        logger.debug("addr:{}, q:{}, r:{}", addr, r, httpResponse.bodyText());
      }
//      logger.debug(httpResponse.body());
    } catch (Exception e) {
      logger.error("registerError:{},msg:{}", serviceId, e.getMessage());
    }
  }

  @Override
  public void deregister(ServiceBean serviceBean, RegistryBean registryBean) {
    String serviceId = String
        .valueOf(ReflectionUtils.getFliedValue(serviceBean.getInterfaceClass(), "serviceId"));
    String addr = "http://" + registryBean.getAddr() + "/v1/agent/service/deregister/" + serviceId + "-" + (IpUtils.localIp());
    HttpResponse httpResponse = HttpRequest.put(addr).send();
    if (logger.isDebugEnabled()) {
      logger.debug("deregister:{},{}", addr, httpResponse.body());
    }
    serviceNameSet.remove(serviceBean.getInterfaceName());
  }

  @Override
  @SuppressWarnings("unchecked")
  public List<RegistryItem> discover(RefererBean refererBean, RegistryBean registryBean) {
    String direct = refererBean.getDirect();
    if (StringUtils.isNotBlank(direct)) {
      List<RegistryItem> registryItems = new ArrayList<>();
      String[] directs = direct.split(":");
      RegistryItem registryItem = new RegistryItem().setAddr(directs[0]).setPort(TypeSafe.anyToInt(directs[1])).setGroup(refererBean.getGroup());
      registryItems.add(registryItem);
      return registryItems;
    }
    String serviceId = String.valueOf(ReflectionUtils.getFliedValue(refererBean.getInterfaceClass(), "serviceId"));
    String addr = "http://" + registryBean.getAddr() + "/v1/catalog/service/" + serviceId + "-" + refererBean.getInterfaceClass().getSimpleName();
    String res = "[]";
    try {
      res = HttpRequest.get(addr).send().bodyText();
      if (StringUtil.isBlank(res) || res.startsWith("unknown")) {
        logger.error("service:{} not find register", serviceId);
        return new ArrayList<>();
      } else {
        if (logger.isDebugEnabled()) {
          logger.debug("discover,serviceId:{},registers:{}", serviceId, StringUtils.replace(StringUtils.replace(res, " ", ""), "\n", ""));
        }
      }
    } catch (Exception e) {
      logger.error("discoverError:{},msg:{}", serviceId, e.getMessage());
      return Lists.newArrayList();
    }

    List<Map<String, Object>> listMap = JsonUtil.fromJsonList(res);
    return listMap.stream().filter(r -> {
      Object registryServiceId = r.get("ServiceID");
      String status = HttpRequest.get("http://" + registryBean.getAddr() + "/v1/agent/health/service/id/" + registryServiceId + "?format=text").send().bodyText();
//      logger.info("serviceId:{},status:{}", registryServiceId, status);
      return "passing".equals(status);
    })
        .map((o) -> new RegistryItem().setId(o.get("ServiceAddress") + "-" + o.get("ServicePort"))
            .setAddr(TypeSafe.anyToString(o.get("ServiceAddress")))
            .setPort(TypeSafe.anyToInt(o.get("ServicePort")))).distinct().collect(Collectors.toList());
  }
}